package store

import (
	"fmt"
	baseConfig "gitee.com/zaiqiang231/go-base-app/base_app/config"
	baseDatabase "gitee.com/zaiqiang231/go-base-app/base_app/database"
	baseLog "gitee.com/zaiqiang231/go-base-app/base_app/log"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"os"
	"sync"
	"time"
)

type Datastore struct {
	DB *gorm.DB
}

var (
	datastore          *Datastore
	once               sync.Once
	defaultLogPath     = "./log_record"
	defaultLogFileName = "mysql.log"
	initializers       []SubInitializer
	cache              map[string]SubInitializer
)

func InitDataStore(opts *baseConfig.MysqlConfig) (*Datastore, error) {
	if opts == nil && datastore == nil {
		return nil, fmt.Errorf("failed to get mysql store fatory, opts:%v datastore: %v", opts, datastore)
	}
	var err error
	var dbIns *gorm.DB
	once.Do(func() {
		baseLog.Infof("InitDataStore %v-%v", opts.Host, opts.Database)
		os.MkdirAll(defaultLogPath, os.ModePerm)
		logFile, _ := os.OpenFile(defaultLogPath+"/"+defaultLogFileName, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0777)
		options := &baseDatabase.Options{
			Host:                  opts.Host,
			Username:              opts.Username,
			Password:              opts.Password,
			Database:              opts.Database,
			MaxIdleConnections:    opts.MaxIdleConnections,
			MaxOpenConnections:    opts.MaxOpenConnections,
			MaxConnectionLifeTime: time.Duration(opts.MaxConnectionLifeTime) * time.Second,
			Logger: logger.New(log.New(logFile, "\r\n", log.LstdFlags), logger.Config{
				SlowThreshold:             200 * time.Millisecond,
				LogLevel:                  logger.Info,
				IgnoreRecordNotFoundError: false,
				Colorful:                  true,
			}),
		}
		dbIns, err = baseDatabase.NewMysqlDb(options)
		if err != nil {
			baseLog.Errorf("failed to open mysql")
			return
		}
		datastore = &Datastore{dbIns}
	})

	if datastore == nil {
		return nil, errors.New("mysql failed to get store")
	}
	return datastore, nil
}

func GetDataStore() *Datastore {
	if datastore == nil {
		baseLog.Errorf("mysql datastore is %v", datastore)
	}
	return datastore
}

type SubInitializer interface {
	GetTable() TableInterface
	InitializerName(table SubInitializer) string
	HasTable(ds *Datastore, table SubInitializer) bool
	CreateTable(ds *Datastore, table SubInitializer) error
	UpdateTable(ds *Datastore, table SubInitializer) error
	InitData(ds *Datastore)
}

type TableInterface interface {
	TableName() string
}

type orderedInitializer struct {
	order int
	SubInitializer
}

func RegisterInit(i SubInitializer) {
	if initializers == nil {
		initializers = []SubInitializer{}
	}
	if cache == nil {
		cache = map[string]SubInitializer{}
	}
	name := i.InitializerName(i)
	if _, existed := cache[name]; existed {
		panic(fmt.Sprintf("Name conflict on %s", name))
	}
	initializers = append(initializers, i)
	cache[name] = i
}

func (d *Datastore) CreateTables() {
	var waitInitTables = []SubInitializer{}
	//避免gorm AutoMigrate 把关联的表给 创建了导致没有执行 数据初始化
	for _, init := range initializers {
		if !init.HasTable(d, init) {
			waitInitTables = append(waitInitTables, init)
		} else {
			if err := init.UpdateTable(d, init); err != nil {
				baseLog.Errorf("%v 表更新失败 %v", init.InitializerName(init), err)
			}
		}
	}
	for _, init := range initializers {
		if err := init.CreateTable(d, init); err != nil {
			baseLog.Errorf("%v 表创建失败 %v", init.InitializerName(init), err)
		}
	}
	for _, init := range waitInitTables {
		baseLog.Infof("%v 表数据初始化", init.InitializerName(init))
		init.InitData(d)
	}
}

type Runnable func(db *gorm.DB)

func (d *Datastore) Execute(runnable Runnable) {
	runnable(d.DB)
}
